/******************************************************************************************************
Copyright (C) 2008 Mestrelab Research S.L. All rights reserved.

This file is part of the MNova scripting toolkit.

Authorized users of MNova Software may use this file freely, but this file is provided AS IS
with NO WARRANTY OF ANY KIND, INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE.
*****************************************************************************************************/

/*globals
        nmr, mainWindow, getTotalRegionPPM, getVisibleRegionPPM, isBetween, print, DBPlugin, MessageBox, Multiplets, NMRSpectrum, Peak, Peaks, Settings */
/*jslint plusplus: true, indent: 4*/


function getPeakQueryInterval(aPrefix, aValue, aTolerance, aInclude) {
    "use strict";
    return aInclude ? (aPrefix + " " + (aValue - aTolerance).toPrecision(6) + " " + (aValue + aTolerance).toPrecision(6)) : "";
}

// Returns a peak search query clause for a single NMR Peak
function getPeakQueryClause(aPeak, aSpec) {
    "use strict";
    var
        settingsKey = "DBSearch/Settings/Peak Search/" + (aSpec.dimCount === 2 ? "2D" : "1D"),
        settings = new Settings(settingsKey),
        posTolerance1 = aSpec.nucleus(1, false) === "1H" ? settings.value("Position/1H", 0.1) : settings.value("Position/Other", 1.0),
        posTolerance2 = aSpec.nucleus(2, false) === "1H" ? settings.value("Position/1H", 0.1) : settings.value("Position/Other", 1.0),
        widthTolerance = settings.value("Width", 1.0),
        intensityTolerance = settings.value("Intensity", 1.0),
        restrictPeakType = settings.value("TypeMustMatch", false),
        restrictPeakFlags = settings.value("FlagsMustMatch", false),
        restrictMultiplicity = false,

        includePosX = true,
        includePosY = aSpec.dimCount === 2,
        includeWidthX = widthTolerance < 1.0 && !isNaN(aPeak.width(1)) && aPeak.width(1) > 0.0,
        includeWidthY = widthTolerance < 1.0 && aSpec.dimCount === 2 && !isNaN(aPeak.width(2)) && aPeak.width(2) > 0.0,
        includeIntensity = intensityTolerance < 1.0 && !isNaN(aPeak.intensity) && aPeak.intensity !== 0.0,

        result = getPeakQueryInterval("Px:", aPeak.delta(1), posTolerance1, includePosX)
        + getPeakQueryInterval(" Py:", aPeak.delta(2), posTolerance2, includePosY)
        + getPeakQueryInterval(" Wx:", aPeak.width(1), aPeak.width(1) * widthTolerance, includeWidthX)
        + getPeakQueryInterval(" Wy:", aPeak.width(2), aPeak.width(2) * widthTolerance, includeWidthY)
        + getPeakQueryInterval(" I:", aPeak.intensity, aPeak.intensity * intensityTolerance, includeIntensity);

    if (restrictPeakType) {
        result += " T: " + aPeak.typeToString();
    }
    if (restrictPeakFlags) {
        result += " F: " + aPeak.flagsToString();
    }
    if (restrictMultiplicity) {
        result += " C: " + aPeak.c13Multiplicity;
    }
    return result;
}

// Returns a multiplet search query clause for a single Multiplet
function getMultipletQueryClause(aMultiplet, aSpec) {
    "use strict";
    var result,
        settingsKey = "DBSearch/Settings/Multiplet Search/" + (aSpec.dimCount === 2 ? "2D" : "1D"),
        settings = new Settings(settingsKey),
        posTolerance = aSpec.nucleus(1, false) === "1H" ? settings.value("Position/1H", 0.1) : settings.value("Position/Other", 1.0),
        widthTolerance = settings.value("Width", 1.0),
        integralTolerance = settings.value("Integral", 1.0),
        width = aMultiplet.rangeMax - aMultiplet.rangeMin,
        integral = aMultiplet.integralValue();
    result = getPeakQueryInterval("Px:", aMultiplet.delta, posTolerance, true)
           + getPeakQueryInterval(" Wx: ", width, width * widthTolerance, widthTolerance < 1.0 && !isNaN(width) && width > 0.0)
           + getPeakQueryInterval(" I: ", integral, integral * integralTolerance, integralTolerance < 1.0 && !isNaN(integral) && integral !== 0.0)
           + " T: " +  aMultiplet.category;
    return result;
}


// Build a peak search query from the given spectrum
function getPeaksQuery(aSpectrum) {
    "use strict";
    var p, peak, includePeak, result, x, y, w,
        searchModes = [ "PURITY", "MIXTURE", "COMPOUND", "STANSIM" ],   // 0, 1, 2, 3  
        spec = new NMRSpectrum(aSpectrum),
        region = getVisibleRegionPPM(spec),
        allPeaks = spec.peaks(),
        peaks = new Peaks(),
        settingsKey = "DBSearch/Settings/Peak Search/",
        dimKey = (aSpectrum.dimCount === 2 ? "2D" : "1D"),
        settings = new Settings(settingsKey),
        searchMode = searchModes[settings.value(dimKey + "/Scoring", 0)],
        limitToRange = settings.value("limitToRange", false);

    //print(settings.getObject("", {}));
    //print(Peak.peakTypeMap);
    //print(Peak.peakFlagMap);

    // If no peaks found, try to pick some
    if (allPeaks.count === 0) {
        print("No peaks found, trying to pick...");
        allPeaks = new Peaks(spec, getTotalRegionPPM(spec));
    }

    // Filter peaks included in query
    //print("region Dim1: from=" + region.from(1) + " to=" + region.to(1) + " Dim2: from=" + region.from(2) + " to="+ region.to(2));
    for (p = 0; p < allPeaks.count; p++) {
        peak = allPeaks.at(p);
        //print ("peak:" + peak.toString());
        //print("1: r.from=" +  region.from(1) + " r.to=" + region.to(1) + " d=" + peak.delta(1));
        //print("2: r.from=" +  region.from(2) + " r.to=" + region.to(2) + " d=" + peak.delta(2));

        includePeak = false;
        if ((peak.type === Peak.Compound || peak.type === Peak.Impurity) && peak.flagsToString() === "None") {
            includePeak = true;
        }
        if (limitToRange) {
            // Filter peaks by current zoom region
            includePeak = includePeak &&
                isBetween(peak.delta(1), region.from(1), region.to(1)) &&
                (spec.dimCount < 2 || isBetween(peak.delta(2), region.from(2), region.to(2)));
        }
        if (includePeak) {
            peaks.append(peak);
        }
    }

    // Build the peaks query
    result = "{0}".format(peaks.count);

    // Optional: Add the peak search mode - COMPOUND/PURITY/MIXTURE/STANSIM. Default on server side is COMPOUND.
    result += " MODE: " + searchMode;

    // Default settings: Included types Compound+Impurity, Excluded flags All
    result += " inc: Compound|Impurity exc: Hidden|Weak|C13Satellite|Rotational|Labile";

    // Add peak clauses:
    for (p = 0; p < peaks.count; p++) {
        peak = peaks.at(p);
        if (searchMode === "STANSIM") {
            x = getPeakQueryInterval("Px:", peak.delta(1), 0, true);
            w = getPeakQueryInterval("Wx:", peak.width(1), 0, true);
            y = getPeakQueryInterval("I:", peak.intensity, 0, true);
            result += "\n{0} {1} {2} {3}".format(p + 1,  x, w, y);
            result += " TT: " + (peak.typeToString() || "None");            // TT is ignored by MnServer, here for debug output
            result += " FF: " + (peak.flagsToString() || "None");           // FF is ignored by MnServer, here for debug output
        } else {
            result += "\n" + (p + 1) + " " + getPeakQueryClause(peak, spec);
        }
    }

    return result;
}

// Build a Multiplet query from the given spectrum.
function getMultipletsQuery(aSpectrum) {
    "use strict";
    var m,
        spec = new NMRSpectrum(aSpectrum),
        multiplets = spec.multiplets(),
        result = "{0}".format(multiplets.count);

    result += " MODE: PURITY";            // Optional: Add the peak search mode - REVERSE/PURITY/MIXTURE. Default is REVERSE.
    for (m = 0; m < multiplets.count; m++) {
        result += "\n" + (m + 1) + " " + getMultipletQueryClause(multiplets.at(m), spec);
    }
    return result;
}

// Retrieve records for all given hits and paste them into Mnova, return list of retrieved molecules
function retrieveHits(aDB, aHits) {
    "use strict";
    var h, aux, items, i,
        //main = mainWindow.activeWindow(),
        //page = main.newPage(),
        docs = [],
        molecules = [];
    for (h = 0; h < aHits.length; h++) {
        aux = mainWindow.newWindow();
        docs.push(aux);
        DBPlugin.getRecord(aDB, aHits.at(h).recordId());
        items = aux.itemCount();
        for (i = 0; i < items; i++) {
            if (aux.item(i).name === "Molecule") {
                //page.addItem(aux.item(i));
                molecules.push(aux.item(i));
            }
        }
    }
    return molecules;
}

// ======================================================================================================

function dbPeakSearchExample() {
    "use strict";
    var query, hits, h,
        db = DBPlugin.client,
        spec = nmr.activeSpectrum(),

        settings = new Settings("DBSearch/Settings"),
        maxHits = settings.value("MaxHits", 10),
        maxRecords = settings.value("MaxRecords", 10),
        minQuality = settings.value("MinHitQuality/PeakSearch", 100),

        // FR #14534: Search only NMR Spectra (not Molecule assignments) iff Search mode is Stans Similarity
        searchModes = [ "PURITY", "MIXTURE", "COMPOUND", "STANSIM" ],   // 0, 1, 2, 3  
        searchMode = searchModes[settings.value("Peak Search/" + (spec.dimCount === 2 ? "2D" : "1D") + "/Scoring", 0)],
        restrictMethod = (searchMode === "STANSIM") ? "NMR Spectrum" : "",
        restrictField = (searchMode === "STANSIM") ? "Peaks1D" : "";

    // Choose database
    if (!(db.connected() && db.database.length > 0)) {
        mainWindow.doAction("dbDatabaseOpen");
    }
    if (!(db.connected() && db.database.length > 0)) {
        MessageBox.critical("Not connected to database.");
        return;
    }

    query = getPeaksQuery(spec, 1);
    print("Peaks query: " + query);
    hits = DBPlugin.search(db, query, "PEAKLIST1A", restrictMethod, restrictField, maxHits, maxRecords, minQuality);
    print("Peak Hits: " + hits.length);
    for (h = 0; h < hits.length; h++) {
        print("    Hit: " + hits.at(h).toString());
    }


    if (spec.multiplets().count === 0) {
        return;      // Server will throw an exception on query with no clauses. Could do automatic multiplet analysis here.        
    }

    query = getMultipletsQuery(spec, 1);
    print("Multiplet query: " + query);
    hits = DBPlugin.search(db, query, "MULTIPLETLIST1A", "", "", maxHits, maxRecords, settings.value("MinHitQuality/MultipletSearch", 100));
    print("Multiplet Hits: " + hits.length);
    for (h = 0; h < hits.length; h++) {
        print("    Hit: " + hits.at(h).toString());
    }

    //retrieveHits(db, hits);     // Uncomment this line to retrieve records from db to Mnova
    //db.disconnect();
}

// The end.
